//
//  HttpService.swift
//  
//
//  Created by tank on 2021/10/18.
//

import Foundation
import Logging
import Alamofire
import Moya
import RxSwift

public class HttpService {
    
    static public let shared = HttpService()
    
    static public var successCode: Int = 200
    
    var logger = Logger(label: "LibNetwork.HttpService")
    
    public lazy var session = {
        var serverTrustManager: ServerTrustManager?
        if let secCertificates = Network.shared.dataSource?.secCertificates(),
           !secCertificates.isEmpty {
            
            var evaluators: [String: PublicKeysTrustEvaluator] = [:]
            secCertificates.forEach { (host: String, certs: [SecCertificate]) in
                evaluators[host] = PublicKeysTrustEvaluator(keys: certs.compactMap({ $0.af.publicKey }))
            }
            
            serverTrustManager = ServerTrustManager(allHostsMustBeEvaluated: false,
                                                    evaluators: evaluators)
        }
        
        return Session(serverTrustManager: serverTrustManager)
    }()
    
    public var apiBaseUrl: String? {
        return Network.shared.serviceAddressRepository?.data?.apiBaseUrl
    }
    
    public var h5BaseUrl: String? {
        return Network.shared.serviceAddressRepository?.data?.h5BaseUrl
    }
    
    private init() {
        logger.logLevel = .debug
    }
    
    public func moyaProvider<T: APITargetType>(stubClosure: @escaping MoyaProvider<T>.StubClosure = MoyaProvider.neverStub) -> MoyaProvider<T> {
        return MoyaProvider<T>(stubClosure: stubClosure,
                               session: session,
                               plugins: [NetworkLoggerPlugin(configuration: NetworkLoggerPlugin.Configuration(output: { [weak self] target, items in
            for item in items {
                self?.logger.info(Logger.Message(stringLiteral: item))
            }
        }, logOptions: [.formatRequestAscURL, .errorResponseBody]))])
    }
    
    public func headers(url: URL) -> [String: String]? {
        guard let host = url.host,
              let apiBaseUrls = Network.shared.serviceAddressRepository?.data?.apiBaseUrls,
              apiBaseUrls.contains(where: {$0.contains(host)}) else {
            return nil
        }
        
        let authMap: [String: String]?
        if let token = Network.shared.token {
            let tokenType: String? = Network.shared.tokenType
            let authorization: String = tokenType == nil ? token : "\(tokenType!) \(token)"
            authMap = ["Authorization": authorization]
        } else {
            authMap = nil
        }
        
        let headers = Network.shared.dataSource?.getHeaders(network: Network.shared)
        
        return Dictionary.merging(value1: authMap, value2: headers)
    }
    
    public func retryRequest(error: Observable<Swift.Error>, maxRetryCount: Int) -> Observable<Int> {
        var requestCount: Int = 0
        
        return error.flatMap { err -> Observable<Int> in
            requestCount += 1
            
            guard requestCount < maxRetryCount, let moyaError = err as? MoyaError,
                  case .underlying = moyaError else {
                return Observable.error(err)
            }
            
            return Observable.timer(.seconds(1), scheduler: MainScheduler.instance)
        }
    }
    
    public func refreshToken(error: Observable<Swift.Error>) -> Observable<Bool> {
        error.flatMap { err -> Observable<Bool> in
            guard let moyaError = err as? MoyaError else {
                return Observable.error(err)
            }
            
            switch moyaError {
            case .statusCode(let response):
                guard response.statusCode == 401,
                      let dataSource = Network.shared.dataSource else {
                    return Observable.error(err)
                }
                
                let dataMap = try? response.mapJSON() as? [String: Any]
                
                guard let dataMap = dataMap,
                      dataMap["code"] as? Int == 100004 else {
                    dataSource.invalidToken(message: dataMap?["msg"] as? String, nil)
                    return Observable.error(err)
                }
                
                return Single<Bool>.create { emmit in
                    dataSource.refreshToken(url: response.request?.url?.absoluteString) { result in
                        guard result else {
                            emmit(.failure(err))
                            return
                        }
                        
                        emmit(.success(true))
                    }
                    
                    return Disposables.create()
                }.asObservable()
            default:
                return Observable.error(err)
            }
        }
    }
}
